package com.hhd.pdf;

import com.hhd.pdf.context.GeneratorContext;
import com.hhd.pdf.exceptions.PdfException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.RectangleReadOnly;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.itextpdf.text.pdf.parser.TextMarginFinder;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public abstract class AbstractPdfGenerator implements PdfGenerator {
    @Override
    public void generate(GeneratorParameters generatorParameters) throws PdfException {
        GeneratorContext context = null;
        List<Element> elements = CommonUtils.newList();
        try {
            context = createContext(generatorParameters);
            elements = doGenerate(context);
            saveToFile(elements, context);
        } catch (Exception e) {
            throw new PdfException(e.getMessage(), e);
        } finally {
            if (!CommonUtils.isEmpty(context)) {
                context.closeFiles();
            }
        }

        generated(context, elements);
    }

    private void generated(GeneratorContext context, List<Element> elements) {
        GeneratorParameters generatorParameters = context.getGeneratorParameters();
        int pageHeight = readPageHeight(generatorParameters);
        try {
            generatorParameters.setPageHeight(pageHeight);
            generateWidthPageHeight(generatorParameters, elements);
        } catch (Exception e) {
            throw new PdfException(e.getMessage(), e);
        }
    }

    public void generateWidthPageHeight(GeneratorParameters generatorParameters, List<Element> elements) throws PdfException {
        com.itextpdf.text.Document document = null;
        try {
            GeneratorContext context = createContext(generatorParameters);
            document = buildDocumentWidthPageHeight(context);
            elements = doGenerate(context);
            saveDocument(context, elements);
        } catch (PdfException e) {
            throw e;
        } catch (Exception e) {
            throw new PdfException(e.getMessage(), e);
        } finally {
            try {
                document.close();
            } catch (Exception e) {
            }
        }
    }

    private com.itextpdf.text.Document buildDocumentWidthPageHeight(GeneratorContext context) throws Exception {
        com.itextpdf.text.Document document = new com.itextpdf.text.Document(
                new RectangleReadOnly(context.getGeneratorParameters().getPageWidth(),
                        context.getGeneratorParameters().getPageHeight()),
                context.getGeneratorParameters().getPagePaddingLeft(),
                context.getGeneratorParameters().getPagePaddingRight(),
                context.getGeneratorParameters().getPagePaddingTop(),
                context.getGeneratorParameters().getPagePaddingBottom());
        PdfWriter.getInstance(document,
                Files.newOutputStream(Paths.get(context.getGeneratorParameters().getOutputPath())));
        document.open();
        context.setDocument(document);
        return document;
    }

    private void saveDocument(GeneratorContext context, List<Element> elements) throws com.itextpdf.text.DocumentException {
        com.itextpdf.text.Document document = context.getDocument();
        if (CommonUtils.isEmpty(elements)) return ;
        for (Element element : elements) {
            document.add(element);
        }
    }

    private int readPageHeight(GeneratorParameters generatorParameters) {
        PdfReader pdfReader = null;
        int numberOfPages = 1;
        try (FileInputStream inputStream = new FileInputStream(generatorParameters.getOutputPath())) {
            pdfReader = new PdfReader(inputStream);
            numberOfPages = pdfReader.getNumberOfPages();
            PdfReaderContentParser parser = new PdfReaderContentParser(pdfReader);
            TextMarginFinder textMarginFinder = parser.processContent(1, new TextMarginFinder());
            return (int) textMarginFinder.getHeight() + 120;
        } catch (IOException ignored) {
            try {
                pdfReader.close();
            } catch (Exception e) {
            }
        }

        int initPageHeight = generatorParameters.getInitPageHeight();
        return numberOfPages * initPageHeight + getPaddingTop(generatorParameters) + getPaddingBottom(generatorParameters);
    }

    private int getPaddingTop(GeneratorParameters generatorParameters) {
        return generatorParameters.getPagePaddingBottom();
    }

    private int getPaddingBottom(GeneratorParameters generatorParameters) {
        return generatorParameters.getPagePaddingTop();
    }

    private void saveToFile(List<Element> elements, GeneratorContext context) {
        try {
            elements.forEach(e -> {
                try {
                    context.getDocument().add(e);
                } catch (DocumentException ex) {
                    throw new RuntimeException(ex);
                }
            });
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public abstract List<Element> doGenerate(GeneratorContext context);

    private GeneratorContext createContext(GeneratorParameters generatorParameters) throws Exception {
        try (InputStream configInputeStream = this.getClass().getResourceAsStream(generatorParameters.getConfigPath())) {
            return GeneratorContext.builder()
                    .generatorParameters(generatorParameters)
                    .document(createWriter(generatorParameters))
                    .xmlDocument(new SAXReader().read(configInputeStream))
                    .build();
        } finally {
        }
    }

    private Document createWriter(GeneratorParameters generatorParameters) throws FileNotFoundException, DocumentException {
        String outputPath = generatorParameters.getOutputPath();
        com.itextpdf.text.Document document = new com.itextpdf.text.Document(new RectangleReadOnly(generatorParameters.getInitPageWidth(), generatorParameters.getInitPageHeight()),
                0, 0, 0, 0);
        PdfWriter.getInstance(document, new FileOutputStream(outputPath));
        document.open();
        return document;
    }

}
